package br.com.mundoj.subscription.client.view;

import java.util.ArrayList;
import java.util.Date;

import br.com.mundoj.subscription.client.Address;
import br.com.mundoj.subscription.client.ContactData;
import br.com.mundoj.subscription.client.FormattedString;
import br.com.mundoj.subscription.client.ILocationData;
import br.com.mundoj.subscription.client.PaymentMethod;
import br.com.mundoj.subscription.client.Person;
import br.com.mundoj.subscription.client.State;
import br.com.mundoj.subscription.client.Subscription;
import br.com.mundoj.subscription.client.Type;
import br.com.mundoj.subscription.client.Person.Gender;
import br.com.mundoj.subscription.client.events.SaveSubscriptionEvent;
import br.com.mundoj.subscription.client.events.SearchAddressByCepEvent;

import com.google.gwt.dom.client.LabelElement;
import com.google.gwt.dom.client.ParagraphElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.TextResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.StackLayoutPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle.MultiWordSuggestion;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.datepicker.client.DateBox;
import com.google.inject.Inject;

public class SubscriptionView extends Composite {
	public interface Binder extends UiBinder<Widget, SubscriptionView> { }
	
	public interface Css extends CssResource {
		String visible();
		String invisible();
	}
	
	public interface Resources extends PaymentIcons {
		TextResource initialEditionsOptions();
		TextResource defaultReferenceOptions();
	}

	@UiField Css css;
	@UiField(provided=true) PaymentIcons payWith;
	@UiField StackLayoutPanel mainPanel;
	
	@UiField ListBox type;
	@UiField ParagraphElement naturalPersonElement, legalEntityElement;
	@UiField LabelElement personLabel, businessLabel;
	@UiField TextBox naturalPersonNumber, legalEntityNumber, name, email;
	
	@UiField TableElement birthAndGenderTable;
	@UiField TableRowElement birthAndGenderRow;
	@UiField RadioButton male, female;
	@UiField DateBox birthDate;
	
	@UiField TextBox cep, city, address, complement, neighborhood;
	@UiField ListBox state;
	
	@UiField TextBox homePhone, workPhone, faxNumber;
	@UiField ToggleButton bankSlip, visa, mastercard, diners, amex;
	
	@UiField ListBox startingEdition;
	@UiField(provided=true) SuggestBox reference;

	@UiField PushButton submitButton;
	
	private final HandlerManager eventBus;
	private final SubscriptionMessages messages;
	private final MessagesWithLookup msgLookup;
	
	private final Person person = new Person();
	private final Subscription subscription = new Subscription(person);
	private final Address addr = person.getAddress();
	private final ContactData contactData = person.getContacts();

	@Inject
	public SubscriptionView(HandlerManager eventBus,
			Resources resources, Binder uiBinder, 
			SubscriptionMessages messages, MessagesWithLookup msgLookup) {
		this.eventBus = eventBus;
		this.payWith = resources;
		this.messages = messages;
		this.msgLookup = msgLookup;
		initReference(resources.defaultReferenceOptions().getText());
		
		initWidget(uiBinder.createAndBindUi(this));
		
		initType(msgLookup);
		birthAndGenderRow.removeFromParent();
		birthDate.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(msgLookup.datePattern())));
		registerBirthDateTextBoxHandler();
		
		initState();
		initStartingEdition(resources.initialEditionsOptions().getText());
	}

	private void initType(MessagesWithLookup msgLookup) {
		type.addItem("");
		type.addItem(msgLookup.getString(Type.NATURAL_PERSON.name().toLowerCase()));
		type.addItem(msgLookup.getString(Type.LEGAL_ENTITY.name().toLowerCase()));
	}

	private void initState() {
		state.addItem("");
		for (State s : State.values())
			state.addItem(s.name, s.name());
	}

	private void initStartingEdition(String text) {
		startingEdition.addItem("");
		String[] rows = text.split("\n");
		for (String r : rows)
			startingEdition.addItem(r, r.split("-")[0].trim());
	}

	private void initReference(String text) {
	    MultiWordSuggestOracle oracle = new MultiWordSuggestOracle();
	    ArrayList<Suggestion> defaultSuggestions = new ArrayList();
	    String[] suggestions = text.split(",");
	    for (int i=0; i<suggestions.length; i++) {
	    	String s = suggestions[i].trim();
	    	defaultSuggestions.add(new MultiWordSuggestion(s, s));
	    }
	    defaultSuggestions.add(new MultiWordSuggestion("", messages.other()));
	    oracle.setDefaultSuggestions(defaultSuggestions);
	    
	    reference = new SuggestBox(oracle);
		reference.getTextBox().addFocusHandler(new FocusHandler() {
			@Override
			public void onFocus(FocusEvent event) {
				reference.showSuggestionList();
			}
		});
	}

	@UiHandler("type")
	void onTypeChange(ChangeEvent e) {
		naturalPersonElement.setClassName(css.invisible());
		legalEntityElement.setClassName(css.invisible());
		businessLabel.setClassName(css.invisible());
		personLabel.setClassName(css.invisible());
		birthAndGenderRow.removeFromParent();
		int i = type.getSelectedIndex();
		if (i==0) {
			mainPanel.setHeight("530px");
			personLabel.setClassName(css.visible());
		}
		else {
			Type t = Type.values()[i-1];
			person.setType(t);
			switch (t) {
			case NATURAL_PERSON:
				naturalPersonElement.setClassName(css.visible());
				personLabel.setClassName(css.visible());
				birthAndGenderTable.appendChild(birthAndGenderRow);
				mainPanel.setHeight("555px");
			break;
			case LEGAL_ENTITY:
				legalEntityElement.setClassName(css.visible());
				businessLabel.setClassName(css.visible());
				mainPanel.setHeight("535px");
				male.setValue(false);
				female.setValue(false);
				birthDate.setValue(null);
				break;
			}
		}
	}

	@UiHandler("cep")
	public void limitCep(KeyDownEvent event) {
		limitTextBoxSize(cep, event, msgLookup.cepFormat().length());
	}

	@UiHandler("naturalPersonNumber")
	public void limitNaturalPersonNumber(KeyDownEvent event) {
		limitTextBoxSize(naturalPersonNumber, event, msgLookup.naturalPersonNumberFormat().length());
	}

	@UiHandler("legalEntityNumber")
	public void limitLegalEntityNumber(KeyDownEvent event) {
		limitTextBoxSize(legalEntityNumber, event, msgLookup.legalEntityNumberFormat().length());
	}

	@UiHandler("homePhone")
	public void limitHomePhone(KeyDownEvent event) {
		limitTextBoxSize(homePhone, event, msgLookup.phoneFormat().length());
	}

	@UiHandler("workPhone")
	public void limitWorkPhone(KeyDownEvent event) {
		limitTextBoxSize(workPhone, event, msgLookup.phoneFormat().length());
	}

	@UiHandler("faxNumber")
	public void limitFaxNumber(KeyDownEvent event) {
		limitTextBoxSize(faxNumber, event, msgLookup.phoneFormat().length());
	}

	private void limitTextBoxSize(TextBox t, KeyDownEvent event, int size) {
		if (t.getCursorPos()==size) {
			int key = event.getNativeKeyCode();
			if (!KeyDownEvent.isArrow(key) && !event.isAnyModifierKeyDown() && 
					!matchesAny(key, KeyCodes.KEY_BACKSPACE, KeyCodes.KEY_ENTER, 
							KeyCodes.KEY_HOME, KeyCodes.KEY_TAB)) {
				event.preventDefault();
				event.stopPropagation();
			}
		}
	}
	
	private boolean matchesAny(int key, int... keyCodes) {
		for (int k : keyCodes)
			if (key==k)
				return true;
		return false;
	}

	@UiHandler("naturalPersonNumber")
	public void formatNaturalPersonNumber(KeyUpEvent event) {
		formatValue(naturalPersonNumber, msgLookup.naturalPersonNumberFormat());
	}

	@UiHandler("legalEntityNumber")
	public void formatLegalEntityNumber(KeyUpEvent event) {
		formatValue(legalEntityNumber, msgLookup.legalEntityNumberFormat());
	}

	@UiHandler("cep")
	public void formatCep(KeyUpEvent event) {
		formatValue(cep, msgLookup.cepFormat());
	}

	@UiHandler({"homePhone", "workPhone", "faxNumber"})
	public void formatFaxNumber(KeyUpEvent event) {
		formatValue((TextBox) event.getSource(), msgLookup.phoneFormat());
	}

	private void formatValue(TextBox tb, String format) {
		int pos = tb.getCursorPos();
		FormattedString fstr = new FormattedString(format, tb.getValue());
		tb.setValue(fstr.formatted);
		tb.setCursorPos(fstr.nextCursorPosition(pos));
	}
	
	@UiHandler({"naturalPersonNumber", "legalEntityNumber"})
	void updateNaturalPersonNumber(ValueChangeEvent<String> event) {
		person.setDocument(event.getValue());
	}

	@UiHandler("name")
	void updateName(ValueChangeEvent<String> event) {
		person.setName(event.getValue());
	}

	@UiHandler("email")
	void updateEmail(ValueChangeEvent<String> event) {
		person.setEmail(event.getValue());
	}
	
	@UiHandler("male")
	void updateMale(ValueChangeEvent<Boolean> event) {
		Gender gender = person.getGender();
		if (gender==null)
			person.setGender(Gender.MALE);
		else {
			// reset value and uncheck radio
			person.setGender(null);
			male.setValue(false);
		}
	}

	@UiHandler("female")
	void updateFemale(ValueChangeEvent<Boolean> event) {
		Gender gender = person.getGender();
		if (gender==null)
			person.setGender(Gender.FEMALE);
		else {
			// reset value and uncheck radio
			person.setGender(null);
			female.setValue(false);
		}
	}
	
	@UiHandler("birthDate")
	void updateBirthDate(ValueChangeEvent<Date> event) {
		person.setBirthDate(new java.sql.Date(event.getValue().getTime()));
	}
	
	void registerBirthDateTextBoxHandler() {
		birthDate.getTextBox().addValueChangeHandler(new ValueChangeHandler<String>() {
			@Override
			public void onValueChange(ValueChangeEvent<String> event) {
				if (event.getValue().isEmpty())
					person.setBirthDate(null);
			}
		});
	}

	@UiHandler("cep")
	void updateLocationData(BlurEvent e) {
		// blur due to retry on timeouts!
		clearAddressData();
		FormattedString formattedCep = new FormattedString(msgLookup.cepFormat(), cep.getValue());
		if (formattedCep.isValid())
			eventBus.fireEvent(new SearchAddressByCepEvent(formattedCep));
	}

	private void clearAddressData() {
		address.setValue("");
		complement.setValue("");
		neighborhood.setValue("");
		city.setValue("");
		state.setSelectedIndex(0);
		
		addr.setAddress("");
		addr.setComplement("");
		addr.setNeighborhood("");
		addr.setCity("");
		addr.setState(null);
		addr.setCep("");
	}

	public void updateAddressData(ILocationData loc) {
		if (loc==null) {
			Window.alert(messages.exception("timeout"));
			cep.setFocus(true);
		}
		else {
			address.setValue(loc.address(), true);
			neighborhood.setValue(loc.neighborhood(), true);
			city.setValue(loc.city(), true);
			
			addr.setState(State.valueOf(loc.state()));
			state.setSelectedIndex(addr.getState().ordinal()+1);
			
			addr.setCep(cep.getValue());
			
			complement.setFocus(true);
		}
	}

	@UiHandler("address")
	void updateAddress(ValueChangeEvent<String> event) {
		addr.setAddress(event.getValue());
	}

	@UiHandler("complement")
	void updateComplement(ValueChangeEvent<String> event) {
		addr.setComplement(event.getValue());
	}

	@UiHandler("neighborhood")
	void updateNeighborhood(ValueChangeEvent<String> event) {
		addr.setNeighborhood(event.getValue());
	}
	
	@UiHandler("city")
	void updateCity(ValueChangeEvent<String> event) {
		addr.setCity(event.getValue());
	}

	@UiHandler("state")
	void updateState(ChangeEvent event) {
		int index = state.getSelectedIndex();
		addr.setState(index==0 ? null : State.values()[index-1]);
	}

	@UiHandler("homePhone")
	void updateHomePhone(ValueChangeEvent<String> event) {
		contactData.setHomePhone(event.getValue());
	}

	@UiHandler("workPhone")
	void updateWorkPhone(ValueChangeEvent<String> event) {
		contactData.setWorkPhone(event.getValue());
	}

	@UiHandler("faxNumber")
	void updateFaxNumber(ValueChangeEvent<String> event) {
		contactData.setFaxNumber(event.getValue());
	}

	@UiHandler({"bankSlip", "visa", "mastercard", "diners", "amex"})
	void updatePaymentType(ClickEvent e) {
		updatePaymentButtonsState((ToggleButton) e.getSource());
	}
	
	@UiHandler({"bankSlip", "visa", "mastercard", "diners", "amex"})
	void updatePaymentType(KeyUpEvent e) {
		if (e.getNativeKeyCode()==' ')
			updatePaymentButtonsState((ToggleButton) e.getSource());
	}
	
	void updatePaymentButtonsState(ToggleButton btn) {
		boolean down = btn.isDown();
		bankSlip.setDown(false);
		visa.setDown(false);
		mastercard.setDown(false);
		diners.setDown(false);
		amex.setDown(false);
		btn.setDown(down);
		
		subscription.setPaymentMethod(!down ? null
			: PaymentMethod.valueOf(btn.getElement().getParentElement().getId()));
	}
	
	@UiHandler("startingEdition")
	void updateStartingEdition(ChangeEvent event) {
		int index = startingEdition.getSelectedIndex();
		subscription.setStartingEdition(new Integer(startingEdition.getValue(index)));
	}

	// works only for typed values
	@UiHandler("reference")
	void updateReference(ValueChangeEvent<String> event) {
		subscription.setReference(event.getValue());
	}

	// works for select a suggestion
	@UiHandler("reference")
	void updateReference(SelectionEvent<Suggestion> event) {
		subscription.setReference(event.getSelectedItem().getDisplayString());
	}

	@UiHandler("submitButton")
	void onClick(ClickEvent e) {
		eventBus.fireEvent(new SaveSubscriptionEvent(subscription));
	}
}